#include <unistd.h>
#include <stdarg.h>
#include <ogc/machine/processor.h>

#include "StartUpProcess.h"
#include "../DeviceControls/DeviceHandler.hpp"
#include "../Controls/ThreadedTaskHandler.hpp"
#include "../SoundOperations/SoundHandler.hpp"
#include "../Network/networkops.h"
#include "../Themes/CTheme.h"
#include "../Memory/mem2.h"
#include "../System/IosLoader.h"
#include "../System/video.h"
#include "../System/input.h"
#include "../System/audio.h"
#include "../System/sys.h"
#include "../stdafx.h"

#define USB_REG_BASE				0x0D040000
#define USB_REG_OP_BASE				(USB_REG_BASE + (read32(USB_REG_BASE) & 0xff))
#define USB_REG_PORTSC_OFF(port)	(0x44 + 4 * port)
#define USB_PORT_CONNECTED(port)	(read32(USB_REG_OP_BASE + USB_REG_PORTSC_OFF(port)) & 0x0F)

extern const u8 logo_SGMGX_png[];
extern const u32 logo_SGMGX_png_size;
extern const u8 progress_bar_png[];
extern const u32 progress_bar_png_size;
extern const u8 progress_bar_startup_empty_png[];
extern const u32 progress_bar_startup_empty_png_size;
extern const u8 progress_bar_startup_outline_png[];
extern const u32 progress_bar_startup_outline_png_size;

StartUpProcess *StartUpProcess::instance = NULL;

StartUpProcess::StartUpProcess()
	: TotalStep(7)
	, Done(0)
	, Canceled(false)
{
	Settings.StartUp = true;
	
	InitGecko();
	
	Settings.EntraceIOS = IOS_GetVersion();
	gprintf("\nStart IOS = %i\n", Settings.EntraceIOS);
	
	MEM2_init(48);
	
	InitScreen();
}

void StartUpProcess::InitScreen()
{
	Theme::LoadFont("");
	
	background = new GuiImage(screenwidth, screenheight, (GXColor) {0, 0, 0, 255});
	
	GXImageData = new GuiImageData(logo_SGMGX_png, logo_SGMGX_png_size);
	GXImage = new GuiImage(GXImageData);
	GXImage->SetAlignment(ALIGN_CENTER | ALIGN_MIDDLE);
	GXImage->SetPosition(screenwidth/2, screenheight/2-50);
	
	titleTxt = new GuiText("Loading...", 24, (GXColor) {255, 255, 255, 255});
	titleTxt->SetAlignment(ALIGN_CENTER | ALIGN_MIDDLE);
	titleTxt->SetPosition(screenwidth/2, screenheight/2+30);
	
	ProgressBarOutlineData = new GuiImageData(progress_bar_startup_outline_png, progress_bar_startup_outline_png_size);
	ProgressBarOutline = new GuiImage(ProgressBarOutlineData);
	ProgressBarOutline->SetAlignment(ALIGN_CENTER | ALIGN_MIDDLE);
	ProgressBarOutline->SetPosition(screenwidth/2, screenheight/2+75);

	ProgressBarEmptyData = new GuiImageData(progress_bar_startup_empty_png, progress_bar_startup_empty_png_size);
	ProgressBarEmpty = new GuiImage(ProgressBarEmptyData);
	ProgressBarEmpty->SetAlignment(ALIGN_LEFT | ALIGN_MIDDLE);
	ProgressBarEmpty->SetPosition(ProgressBarOutline->GetLeft(), screenheight/2+75);
	ProgressBarEmpty->SetTileHorizontal(100);
	
	ProgressBarData = new GuiImageData(progress_bar_png, progress_bar_png_size);
	ProgressBar = new GuiImage(ProgressBarData);
	ProgressBar->SetAlignment(ALIGN_LEFT | ALIGN_MIDDLE);
	ProgressBar->SetPosition(ProgressBarOutline->GetLeft(), screenheight/2+75);
	
	messageTxt = new GuiText((char*)NULL, 22, (GXColor) {255, 255, 255, 255});
	messageTxt->SetAlignment(ALIGN_CENTER | ALIGN_MIDDLE);
	messageTxt->SetPosition(screenwidth/2, screenheight/2+120);
	
	cancelTxt = new GuiText("Press B to cancel", 18, (GXColor) {255, 255, 255, 255});
	cancelTxt->SetAlignment(ALIGN_CENTER | ALIGN_MIDDLE);
	cancelTxt->SetPosition(screenwidth/2, screenheight/2+150);
	
	trigB = new GuiTrigger();
	trigB->SetButtonOnlyTrigger(-1, WPAD_BUTTON_B | WPAD_CLASSIC_BUTTON_B, PAD_BUTTON_B);
	
	CancelBtn = new GuiButton(0, 0);
	CancelBtn->SetTrigger(trigB);
	CancelBtn->Clicked.connect(this, &StartUpProcess::OnCancelClick);
	
	DrawCancel = false;
}

StartUpProcess::~StartUpProcess()
{
	delete background;
	delete GXImage;
	delete ProgressBarOutline;
	delete ProgressBarEmpty;
	delete ProgressBar;
	
	Resources::Remove(GXImageData);
	Resources::Remove(ProgressBarOutlineData);
	Resources::Remove(ProgressBarEmptyData);
	Resources::Remove(ProgressBarData);
	
	delete titleTxt;
	delete messageTxt;
	delete cancelTxt;
	
	delete CancelBtn;
	
	delete trigB;
	
	Settings.StartUp = false;
}

void StartUpProcess::Progress()
{
	ProgressBar->SetTileHorizontal(100*Done/TotalStep);
	
	Done++;
}

void StartUpProcess::ParseArguments(int argc, char *argv[])
{
	//! The arguments override
	for(int i = 0; i < argc; ++i)
	{
		if(!argv[i]) continue;
		
		gprintf("Boot argument %i: %s\n", i+1, argv[i]);
		
		char *ptr = strcasestr(argv[i], "-ios=");
		if(ptr)
		{
			int ios = atoi(ptr+strlen("-ios="));
			if(ios == 58)
				Settings.BootIOS = ios;
			else
				Settings.BootIOS = LIMIT(ios, 200, 255);
		}
	}
}

void StartUpProcess::TextFade(int direction)
{
	if(direction > 0)
	{
		for(int i = 0; i < 255; i += direction)
		{
			messageTxt->SetAlpha(i);
			Draw();
		}
		messageTxt->SetAlpha(255);
		Draw();
	}
	else if(direction < 0)
	{
		for(int i = 255; i > 0; i += direction)
		{
			messageTxt->SetAlpha(i);
			Draw();
		}
		messageTxt->SetAlpha(0);
		Draw();
	}
}

void StartUpProcess::SetTextf(const char * format, ...)
{
	char * tmp = NULL;
	va_list va;
	va_start(va, format);
	if((vasprintf(&tmp, format, va) >= 0) && tmp)
	{
		TextFade(-40);
		gprintf(tmp);
		messageTxt->SetText(tmp);
		TextFade(40);
	}
	va_end(va);

	if(tmp)
		free(tmp);
}

bool StartUpProcess::USBSpinUp()
{
	//! if we have AHBPROT check if some device is connected to the ports before trying to mount them
	//! otherwise just try and wait till it times out
	if(AHBPROT_DISABLED)
	{
		if((Settings.USBPort == 0) && !USB_PORT_CONNECTED(0))
			return false;
		else if ((Settings.USBPort == 1) && !USB_PORT_CONNECTED(1))
			return false;
		else if (!USB_PORT_CONNECTED(0) && !USB_PORT_CONNECTED(1))
			return false;
	}
	
	DrawCancel = true;
	
	bool started0 = false;
	bool started1 = false;
	Timer countDown;
	
	const DISC_INTERFACE * handle0 = NULL;
	const DISC_INTERFACE * handle1 = NULL;
	if(Settings.USBPort != 1)
		handle0 = DeviceHandler::GetUSB0Interface();
	if(Settings.USBPort != 0 && USB_PORT_CONNECTED(1))
		handle1 = DeviceHandler::GetUSB1Interface();
	
	do
	{
		if(handle0)
			started0 = (handle0->startup() && handle0->isInserted());
		
		if(handle1)
			started1 = (handle1->startup() && handle1->isInserted());
		
		if ((!handle0 || started0) && 
			(!handle1 || started1))
			break;
		
		UpdatePads();
		for(int i = 0; i < 4; ++i)
			CancelBtn->Update(&userInput[i]);
		
		if(Canceled)
		{
			Canceled = false;
			break;
		}
		
		messageTxt->SetTextf("Waiting for HDD: %i sec left\n", 20-(int)countDown.elapsed());
		Draw();
		usleep(50000);
	}
	while(countDown.elapsed() < 20.f);
	
	DrawCancel = false;
	
	return (started0 || started1);
}

void StartUpProcess::OnCancelClick(GuiButton *sender UNUSED, int pointer UNUSED, const POINT &p UNUSED)
{
	Canceled = true;
}

void StartUpProcess::Run(int argc, char *argv[])
{
	Sys_CheckvWii();
	if(Settings.vWii)
		gprintf("Running on vWii\n");
	
	SetupPads();
	
	SetTxt("Initialize sd card\n");
	sleep(1);
	DeviceHandler::Instance()->MountSD();
	
	SetTxt("Initialize usb device\n");
	StartUpProcess::USBSpinUp();
	DeviceHandler::Instance()->MountAllUSB(false);
	
	SetTxt("Loading settings\n");
	bool bSettingsLoaded = false;
	gprintf("Loading config...%s\n", (bSettingsLoaded = Settings.Load()) ? "done" : "failed");
	
	ParseArguments(argc, argv);
	
	if(Settings.BootIOS != Settings.EntraceIOS)
	{
		//! Load now the cios setup in the settings
		TotalStep++;
		Progress();
		SetTextf("Loading cIOS %i\n", Settings.BootIOS);
		
		ShutdownPads();
		DeviceHandler::DestroyInstance();
		if(IosLoader::LoadAppCios() < 0)
		{
			SetTextf("SaveGameGX requires IOS 58 or cIOS 247-250 to work.\n");
			sleep(3);
		}
		SetupPads();
		DeviceHandler::Instance()->MountSD();
		StartUpProcess::USBSpinUp();
		DeviceHandler::Instance()->MountAllUSB(false);
		
		//! if settings were not loaded before, reloading here again.
		if(!bSettingsLoaded)
			Settings.Load();
	}
	else if(Settings.BootIOS == 58)
	{
		ShutdownPads();
		DeviceHandler::DestroyInstance();
		
		IosLoader::PatchIos58();
		
		SetupPads();
		DeviceHandler::Instance()->MountSD();
		StartUpProcess::USBSpinUp();
		DeviceHandler::Instance()->MountAllUSB(false);
	}
	
	SetTextf("Loaded cIOS %i R%i\n", IOS_GetVersion(), IOS_GetRevision());
	sleep(1);
	
	if(!IosLoader::IsHermesIOS() && !IosLoader::IsD2X())
	{
		Settings.USBPort = 0;
	}
	else if(Settings.USBPort == 1 && USBStorage2_GetPort() != Settings.USBPort)
	{
		TotalStep++;
		Progress();
		SetTextf("Changing USB Port to %i\n", Settings.USBPort);
		DeviceHandler::Instance()->UnMountAllUSB();
		DeviceHandler::Instance()->MountAllUSB();
	}
	else if(Settings.USBPort == 2)
	{
		TotalStep++;
		SetTxt("Mounting USB Port to 1\n");
		DeviceHandler::Instance()->MountUSBPort1();
	}
	
	if (!DeviceHandler::Instance()->SD_Inserted() &&
		!DeviceHandler::Instance()->USB0_Inserted() &&
		!DeviceHandler::Instance()->USB1_Inserted())
	{
		SetTextf("No Device Found.\n");
		sleep(2);
		Settings.LoadedBrowser = SAVE_BROWSER;
	}
	
	//! Init the rest of the System
	SetTxt("Initialize System\n");
	InitAudio();
	Sys_Init();
	DeviceHandler::Instance()->MountNAND();
	DeviceHandler::Instance()->MountAllGC();
	
	Sys_CheckSneek();
	if(Settings.SneekRunning)
		gprintf("Sneek running\n");
	
	//! Set UTF 8 locale
	setlocale(LC_CTYPE, "C-UTF-8");
	setlocale(LC_MESSAGES, "C-UTF-8");

	//! Initialize threads
	SoundHandler::Instance();
	InitNetworkThread();
	ThreadedTaskHandler::Instance();
	
	SetTxt("Loading Theme\n");
	gprintf("Loading font...%s\n", Theme::LoadFont(Settings.UseSystemFont ? "" : Settings.CustomFontPath) ? "done" : "failed (using default)");
	gprintf("Loading theme...%s\n", Theme::Load(Settings.Theme) ? "done" : "failed (using default)");
	gprintf("Loading language...%s\n", Settings.LoadLanguage(Settings.LanguageFile, CONSOLE_DEFAULT) ? "done" : "failed");
	
	SetTxt("Loading Gui\n");
}

void StartUpProcess::Draw()
{
	background->Draw();
	GXImage->Draw();
	titleTxt->Draw();
	messageTxt->Draw();
	ProgressBarEmpty->Draw();
	ProgressBar->Draw();
	ProgressBarOutline->Draw();
	
	if(DrawCancel)
		cancelTxt->Draw();
	
	Menu_Render();
}
